/**
 * Copyright 2018-2020 stylefeng & fengshuonan (https://gitee.com/stylefeng)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.authstr.basic.aop;

import com.authstr.basic.exception.BaseExceptionEnum;
import com.authstr.basic.exception.ErrorException;
import com.authstr.basic.exception.InvalidKaptchaException;
import com.authstr.basic.exception.ServiceException;
import com.authstr.basic.reqres.response.ErrorResponseData;
import com.authstr.basic.reqres.utils.WebUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.List;


/**
 * 全局的的异常拦截器（拦截所有的控制器）（带有@RequestMapping注解的方法上都会拦截）
 *
 * @author fengshuonan
 * @date 2016年11月12日 下午3:19:56
 */
@ControllerAdvice
/**
 *ControllerAdvice注解 用于对Controller进行“切面”环绕
 * 其用法主要有三点：
 * 结合方法型注解@ExceptionHandler，用于捕获Controller中抛出的指定类型的异常，从而达到不同类型的异常区别处理的目的；
 * 结合方法型注解@InitBinder，用于request中自定义参数解析方式进行注册，从而达到自定义指定格式参数的目的；
 * 结合方法型注解@ModelAttribute，表示其标注的方法将会在目标Controller方法执行之前执行。
 */

@Order(-1)
/**
 *注解@Order的作用是定义Spring IOC容器中Bean的执行顺序的优先级，而不是定义Bean的加载顺序
 * 默认是最低优先级,值越小优先级越高
 */
public class GlobalExceptionHandler {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 用户未登录异常
     */
    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String unAuth(AuthenticationException e, Model model) {
        model.addAttribute("tips", "请登录");
        log.error("用户未登陆：", e);
        return "/login";
    }

    /**
     * 账号被冻结异常
     */
    @ExceptionHandler(DisabledAccountException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String accountLocked(DisabledAccountException e, Model model) {
        String account = WebUtils.getRequest().getParameter("account");
//        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "账号被冻结", getIp()));
        model.addAttribute("tips", "账号被冻结");
        return "/login";
    }

    /**
     * 账号密码错误异常
     */
    @ExceptionHandler(CredentialsException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String credentials(CredentialsException e, Model model) {
//        String username = getRequest().getParameter("username");
//        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "账号密码错误", getIp()));
        model.addAttribute("tips", "账号密码错误");
        return "/login";
    }

    /**
     * 验证码错误异常
     */
    @ExceptionHandler(InvalidKaptchaException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String credentials(InvalidKaptchaException e, Model model) {
        String account = WebUtils.getRequest().getParameter("account");
//        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "验证码错误", getIp()));
        model.addAttribute("tips", "验证码错误");
        return "/login";
    }

    /**
     * 无权访问该资源异常
     */
    @ExceptionHandler({UndeclaredThrowableException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public ErrorResponseData credentials(UndeclaredThrowableException e) {
        WebUtils.getRequest().setAttribute("tip", "权限异常");
        log.error("权限异常!", e);
        return new ErrorResponseData(BaseExceptionEnum.NO_PERMITION.getCode(), BaseExceptionEnum.NO_PERMITION.getMessage(),e.getMessage());
    }

    /**
     * 无授权访问异常
     */
    @ExceptionHandler({UnauthorizedException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public ErrorResponseData authorization(UnauthorizedException e) {
        WebUtils.getRequest().setAttribute("tip", "无访问授权");
        log.error("无访问授权!", e);
        return new ErrorResponseData(BaseExceptionEnum.NO_AUTHORIZATION.getCode(), BaseExceptionEnum.NO_AUTHORIZATION.getMessage(),e.getMessage());
    }



    /**
     * 捕获数据绑定异常
     * @param ex
     * @return
     */
    @ExceptionHandler(value={BindException.class, MethodArgumentNotValidException.class})
    @ResponseBody
    public ErrorResponseData bindExceptionHandler(Exception ex) {
        ErrorResponseData res=new ErrorResponseData();
        //出现参数不正确的异常,在返回值显示提示信息,具体错误消息进行记录和打印
        //设置为参数错误
        res.addEnumInfo(BaseExceptionEnum.PARA_ERROR);
        List<FieldError> fieldErrors=null;
        if(ex instanceof BindException){
            fieldErrors=((BindException)ex).getBindingResult().getFieldErrors();
        }else if(ex instanceof  MethodArgumentNotValidException){
            fieldErrors=((MethodArgumentNotValidException)ex).getBindingResult().getFieldErrors();
        }else{
            return res;
        }
        List<String> allError=new ArrayList<>();
        //将第一个错误信息作为响应的错误信息
        res.setMessage(fieldErrors.get(0).getDefaultMessage());
        //记录其他错误信息
        for (FieldError error:fieldErrors){
            allError.add(error.getDefaultMessage());
        }
        res.setData(allError);
        return res;
    }


    /**
     * 捕获消息型的异常
     * @param ex
     * @return
     */
    @ExceptionHandler(value={ServiceException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData exceptionHandler(ServiceException ex) {
        ErrorResponseData res=new ErrorResponseData();
        res.addExceptionInfo(ex);
        log.error("业务异常:", ex);
        return res;
    }


    /**
     * 捕获出错型的异常
     * @param ex
     * @return
     */
    @ExceptionHandler(value={ErrorException.class})
    @ResponseBody
    public ErrorResponseData exceptionHandler(ErrorException ex) {
        ErrorResponseData res=new ErrorResponseData();
        //出现错误型异常,在返回值显示服务器异常,具体错误消息进行记录和打印
        ex.printStackTrace();
        log.error("系统执行出现异常:" + ex.getMessage());
        return res;
    }

    /**
     * 捕获其他异常
     * @param ex
     * @return
     */
    @ExceptionHandler(value={Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData exceptionHandler(Exception ex) {
        ErrorResponseData res=new ErrorResponseData();
        //不确定的异常,在返回值显示未知异常,具体错误消息进行记录和打印
        res.addEnumInfo(BaseExceptionEnum.UNKNOWN_ERROR);
        ex.printStackTrace();
        this.log.error( "系统出现未知异常 :" + ex.getMessage());
        return res;
    }
}
